using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Baik.Interface.Execution;
using Baik.Interface.Expression;
using Baik.Interface.Framework;
using IUndefined = Baik.Interface.Execution.IUndefined;

namespace Baik.Execution
{
    public class Function : Object , IFunction
    {
        public Function(IInjector inject) : base(inject)
        {
            TestScenarios = new List<GivenObject>();
            CatchFunctions = new List<IFunction>();
            Dictionary.Add("given",  BuiltInFactory.Function(inject, Given));
            Dictionary.Add("test",  BuiltInFactory.Function(inject, Test));
            //Dictionary.Add("function", new NonRecursingScope(inject));
            Dictionary.Add("catch",  BuiltInFactory.Function(inject, Catch));
        }
        private List<IFunction> CatchFunctions { get; set; }
        private IObject Catch(IEnumerable<IObject> arg)
        {
            CatchFunctions.AddRange(arg.OfType<IFunction>());
            return this;
        }

        


        public IScope HostingScope { get; set; }

        public override IObject Get(string name)
        {
         
            IObject rtn = TryGet(name, out rtn) ? rtn : Inject.Instantiate<IUndefined>();;
            if (rtn is IUndefined && HostingScope != null) rtn = HostingScope.Get(name);
            if (rtn is IUndefined && DeclarationScope != null) rtn = DeclarationScope.Get(name);
            return rtn;
           
        }

        public virtual IObject Call(IEnumerable<IObject> paramas)
        {
            try
            {
                var plist = Params as IReference[] ?? Params.ToArray();
                var ilist = paramas as IObject[] ?? paramas.ToArray();
                var arg = Inject.Instantiate<IArray>();
                arg.Items = ilist;
                Assign("arguments", arg);
                for (int i = 0; i < plist.Length; i++)
                {
                    Dictionary.Remove(plist[i].Name);
                    if (i < ilist.Count())
                    {
                        Assign(plist[i].Name, ilist[i]);
                    }
                }
                IObject last = Inject.Instantiate<IUndefined>();
                foreach (var exp in Expressions)
                {
                    
                    last = exp.Evaluate(this);
                    
                    if (exp is Baik.Interface.Expression.IReturn) return last;
                }
                return last;
            }
            catch (Exception ex)
            {
                var rtn = Inject.Instantiate<IObject>();
                var msg = Inject.Instantiate<IString>();
                msg.Value = ex.Message;
                rtn.Assign("message", msg);

                IObject frtn = null;
                foreach (var cf in CatchFunctions)
                {
                    frtn = cf.Call(new IObject[] {rtn});
                }
                return frtn;
            }
            
        }

        public IEnumerable<IReference> Params { get; set; }
        public IEnumerable<IExpression> Expressions { get; set; }

        private string paramstring()
        {
            var plist = Params as IReference[] ?? Params.ToArray();
            var sw = new StringWriter();
            for (int i = 0; i < plist.Length; i++)
            {
                if (i > 0) sw.Write(", ");
                sw.Write(plist[i].Name);
            }
            return sw.ToString();
        }

        private string expressionstring()
        {
            var elist = Expressions as IExpression[] ?? Expressions.ToArray();

            var sw = new StringWriter();
            for (int i = 0; i < elist.Length; i++)
            {
                if(elist[i] != null)sw.WriteLine(elist[i].AsString() + ";\r\n");
            }
            return sw.ToString();
        }
        public string AsString()
        {
            return "(" + paramstring() + ")\r\n{\r\n" + expressionstring() + "}";
        }

        private IObject Given(IEnumerable<IObject> arg)
        {
            var rtn = new GivenObject(Inject) {Functions = new List<IFunction>(arg.OfType<IFunction>())};
            TestScenarios.Add(rtn);
            return rtn;
        }

        private IObject Test(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<IArray>();
            rtn.Items = TestScenarios.Select(ts => ts.Test(new List<IObject>()));
            return rtn;
        }

        public List<GivenObject> TestScenarios { get; private set; }
        public class GivenObject : Object, IObject
        {
            public List<ThenObject> Outcomes { get; private set; }
            public IEnumerable<IFunction> Functions { get; set; }
            private List<ThenObject> Tests;
            public GivenObject(IInjector inject)
                : base(inject)
                {
                    Tests = new List<ThenObject>();
                    this.Outcomes = new List<ThenObject>();
                    Dictionary.Add("then",  BuiltInFactory.Function(inject, Then));
                    Dictionary.Add("test",  BuiltInFactory.Function(inject, Test));
                }

            private IObject Then(IEnumerable<IObject> arg)
            {
                var rtn = new ThenObject(Inject) { Functions = new List<IFunction>(arg.OfType<IFunction>()) };
                return rtn;        
            }

            public IObject Test()
            {
                var rtn = Inject.Instantiate<IArray>();
                rtn.Items = Outcomes.Select(f => f.Test());
                return rtn;    
            }
            public IObject Test(IEnumerable<IObject> arg)
            {
                return Test();
            }
        }
        public class ThenObject : Object, IObject
        {
            public ThenObject(IInjector inject)
                : base(inject)
            {
                Dictionary.Add("test",  BuiltInFactory.Function(inject, Test));

            }
            public IEnumerable<IFunction> Functions { get; set; }
            public IObject Test(IEnumerable<IObject> prms)
            {
                return Test();
            }

            public IObject Test()
            {
                var rtn = Inject.Instantiate<IArray>();
                rtn.Items = Functions.Select(f => f.Call(new List<IObject>()));
                return rtn;    
            }
        }
    }
}